from flask import Flask, request, send_from_directory, jsonify, send_file, make_response
import os
import shutil
import zipfile
import re
import socket
# from werkzeug.utils import secure_filename
from urllib.parse import unquote

app = Flask(__name__, static_folder="static")

# Constants
MAX_FILE_NAME_LENGTH = 255
VALID_FILE_NAME_PATTERN = re.compile(r'^[^<>:"/\\|?*]*$')
ROOT_PATH = './data'
CURRENT_PATH = ''

# Configuration
app.config['UPLOAD_FOLDER'] = ROOT_PATH
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024 * 1024  # Default 1GB

def clean_filename(filename: str) -> str:
    # 只保留文件名本体（防止用户传路径名）
    filename = os.path.basename(filename)

    # 替换非法字符（不影响中文）
    # Windows 禁止使用的字符：\/:*?"<>|，统一替换为下划线 _
    filename = re.sub(r'[\\/:*?"<>|]', '_', filename)

    # 防止隐藏文件（如 .env）、空文件名等
    if filename.startswith('.'):
        filename = '_' + filename

    # 再保险地移除路径穿越的点
    if '..' in filename:
        filename = filename.replace('..', '_')

    return filename

def parse_storage_size(size_str):
    if size_str in ("0", "0K", "0M", "0G", "0T"):
        return 0

    size_str = size_str.upper()
    if size_str.endswith('T'):
        size = float(size_str[:-1]) * (1 << 40)
    elif size_str.endswith('G'):
        size = float(size_str[:-1]) * (1 << 30)
    elif size_str.endswith('M'):
        size = float(size_str[:-1]) * (1 << 20)
    elif size_str.endswith('K'):
        size = float(size_str[:-1]) * (1 << 10)
    else:
        size = float(size_str)

    return int(size)

def validate_file_name(file_name):
    if len(file_name) > MAX_FILE_NAME_LENGTH:
        return False, "文件名过长"
    if not VALID_FILE_NAME_PATTERN.match(file_name):
        return False, "文件名包含非法字符"
    if '..' in file_name:
        return False, "文件名包含非法路径"
    return True, ""

def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # doesn't even have to be reachable
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP

@app.route('/')
def index():
    return send_from_directory('static', 'index.html')

@app.route('/<path:path>')
def static_files(path=''):
    return send_from_directory(os.path.join('static'), 'index.html')

@app.route('/api/addFolder', methods=['POST'])
def add_folder():
    data = request.json
    name = data.get('name')
    folder_path = data.get('path', '')
    full_path = os.path.join(app.config['UPLOAD_FOLDER'], folder_path, name)
    is_valid, error_message = validate_file_name(name)
    if not is_valid:
        return error_message, 400
    print('full_path:', full_path)
    os.makedirs(full_path, exist_ok=True)
    return jsonify({"message": "文件夹已创建"}), 200

@app.route('/api/delete', methods=['POST']) # 改成当前目录
def delete_file_or_folder():
    name = request.json.get('name')
    full_path = os.path.join(CURRENT_PATH, name)
    try:
        if os.path.isdir(full_path):
            shutil.rmtree(full_path)
        else:
            os.remove(full_path)
        return jsonify({"message": "文件或文件夹已删除"}), 200
    except Exception as e:
        return str(e), 500

@app.route('/api/rename', methods=['POST']) # 这里也改成当前路径了
def rename_file_or_folder():
    data = request.json
    old_name = data.get('oldName')
    new_name = clean_filename(data.get('newName'))
    old_full_path = os.path.join(CURRENT_PATH, old_name)
    new_full_path = os.path.join(os.path.dirname(old_full_path), new_name)
    try:
        os.rename(old_full_path, new_full_path)
        return jsonify({"message": "文件或文件夹已重命名"}), 200
    except Exception as e:
        return str(e), 500

@app.route('/api/download', methods=['GET']) # 这里要用current_path
def download_file_or_folder():
    name = unquote(request.args.get('name', ''))
    full_path = os.path.join(CURRENT_PATH, name)
    if not os.path.exists(full_path):
        return "文件不存在", 404
    if os.path.isdir(full_path):
        zip_filename = f"{name}.zip"
        zip_path = os.path.join(CURRENT_PATH, zip_filename)
        if os.path.exists(zip_path):
            os.remove(zip_path)
        with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for root, dirs, files in os.walk(full_path):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.relpath(file_path, start=CURRENT_PATH)
                    zipf.write(file_path, arcname)
        # Create a response with explicit headers
        response = make_response(send_file(
            zip_path,
            mimetype='application/zip',
            as_attachment=True,
            download_name=zip_filename  # Ensure the filename ends with .zip
        ))
        # Explicitly set Content-Disposition to enforce the filename
        response.headers['Content-Disposition'] = f'attachment; filename="{zip_filename}"'
        return
        # return send_file(zip_path, as_attachment=True, download_name=os.path.basename(zip_filename), mimetype='application/zip')
    else:
        return send_file(full_path, as_attachment=True, download_name=os.path.basename(full_path))

@app.route('/api/upload', methods=['POST']) # 这个前端传进来path了不需要再改了
def upload_file():
    if 'file' not in request.files:
        return "没有找到文件", 400
    file = request.files['file']
    # path = request.form.get('path', '')
    if file.filename == '':
        return "没有选择文件", 400
    if file:
        is_valid, error_message = validate_file_name(file.filename)
        if not is_valid:
            return error_message, 400
        full_path = os.path.join(CURRENT_PATH, clean_filename(file.filename))
        os.makedirs(os.path.dirname(full_path), exist_ok=True)
        file.save(full_path)
        return jsonify({"message": "文件已上传"}), 200

@app.route('/api/files', methods=['POST']) # 这个每次修改current_path
def list_files():
    path = request.json.get('path', '.')
    full_path = os.path.join(app.config['UPLOAD_FOLDER'], path)
    try:
        files = os.listdir(full_path)
        file_info = []
        for file in files:
            file_path = os.path.join(full_path, file)
            info = os.stat(file_path)
            file_info.append({
                "name": file,
                "size": info.st_size,
                "modTime": info.st_mtime,
                "isDir": os.path.isdir(file_path)
            })
        global CURRENT_PATH
        CURRENT_PATH = os.path.join(app.config['UPLOAD_FOLDER'], path)
        return jsonify(file_info), 200
    except Exception as e:
        return str(e), 500

if __name__ == '__main__':
    port = 8011
    ip = get_local_ip()
    print(f"启动HTTP服务: http://{ip}:{port}/static 服务所在的文件目录是 {ROOT_PATH}")
    app.run(host='0.0.0.0', port=port)

